package com.p6spy.engine.spy;

import com.p6spy.engine.common.ConnectionInformation;
import com.p6spy.engine.common.P6LogQuery;
import com.p6spy.engine.event.JdbcEventListener;
import com.p6spy.engine.wrapper.ConnectionWrapper;

import java.sql.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;

/**
 * P6Spy的JDBC驱动实现
 */
public class P6SpyDriver implements Driver {

    private static final Driver instance = new P6SpyDriver();

    /**
     * 私有静态成员变量，用于存储JdbcEventListenerFactory实例
     * JdbcEventListenerFactory主要用于创建和管理JDBC事件监听器对象
     */
    private static JdbcEventListenerFactory jdbcEventListenerFactory;

    /*
     * 静态初始化块，在类加载时执行，尝试将P6SpyDriver实例注册到DriverManager中
     */
    static {
        try {
            // 尝试注册P6SpyDriver的单例实例到JDBC驱动管理器
            DriverManager.registerDriver(P6SpyDriver.instance);

        } catch (SQLException e) {
            // 如果注册过程中出现SQLException异常，则抛出IllegalStateException异常，附带描述信息和原生异常
            throw new IllegalStateException("Could not register P6SpyDriver with DriverManager", e);
        }
    }


    /**
     * 判断是否接受指定的URL作为数据库连接
     * URL应以"jdbc:p6spy:"开头
     *
     * @param url 连接URL
     * @return 如果接受该URL则返回true，否则返回false
     */
    @Override
    public boolean acceptsURL(final String url) {
        return url != null && url.startsWith("jdbc:p6spy:");
    }

    /**
     * 提取实际的JDBC连接URL，移除前缀"p6spy:"
     *
     * @param url 连接URL
     * @return 去掉前缀后的实际URL
     */
    private String extractRealUrl(String url) {
        return acceptsURL(url) ? url.replace("p6spy:", "") : url;
    }

    /**
     * 获取已注册的全部JDBC驱动列表
     *
     * @return 返回包含所有已注册JDBC驱动的列表
     */
    static List<Driver> registeredDrivers() {
        // 创建一个用于存储已注册JDBC驱动的ArrayList
        List<Driver> result = new ArrayList<Driver>();

        // 使用DriverManager.getDrivers()获取已注册的所有JDBC驱动枚举
        for (Enumeration<Driver> driverEnumeration = DriverManager.getDrivers();
            // 检查枚举中是否还有更多的元素（即未处理的驱动）
             driverEnumeration.hasMoreElements(); ) {

            // 将当前枚举中的下一个元素（一个JDBC驱动实例）添加到结果列表中
            result.add(driverEnumeration.nextElement());
        }
        // 返回包含所有已注册JDBC驱动的列表
        return result;
    }


    /**
     * 创建数据库连接，同时启用P6Spy日志和监听器
     *
     * @param url        连接URL
     * @param properties 连接属性
     * @return 包装了真实连接的P6Spy代理连接
     * @throws SQLException 当创建连接时发生异常
     */
    @Override
    public Connection connect(String url, Properties properties) throws SQLException {
        // 检查传入的URL是否存在，若不存在则抛出异常
        if (url == null) {
            throw new SQLException("url is required");
        }

        // 检查当前驱动程序是否接受此URL，如果不接受则返回null
        if (!acceptsURL(url)) {
            return null;
        }

        // 查找与给定URL匹配的真实数据库驱动
        Driver passThru = findPassthru(url);

        // 输出调试信息
        P6LogQuery.debug("this is " + this + " and passthru is " + passThru);

        // 记录开始时间（用于计算获取连接所需时间）
        final long start = System.nanoTime();

        // 如果P6SpyDriver.jdbcEventListenerFactory尚未初始化，则进行加载
        if (P6SpyDriver.jdbcEventListenerFactory == null) {
            P6SpyDriver.jdbcEventListenerFactory = JdbcEventListenerFactoryLoader.load();
        }

        // 创建一个新的数据库连接实例变量
        final Connection conn;

        // 创建一个JdbcEventListener实例以监听数据库事件
        final JdbcEventListener jdbcEventListener = P6SpyDriver.jdbcEventListenerFactory.createJdbcEventListener();

        // 从真实驱动中构建ConnectionInformation对象
        final ConnectionInformation connectionInformation = ConnectionInformation.fromDriver(passThru);

        // 设置连接信息中的URL
        connectionInformation.setUrl(url);

        // 在尝试获取连接前调用监听器的onBeforeGetConnection方法
        jdbcEventListener.onBeforeGetConnection(connectionInformation);

        try {
            // 使用真实驱动通过extractRealUrl解析后的URL建立数据库连接
            conn = passThru.connect(extractRealUrl(url), properties);

            // 将已获取的连接设置到ConnectionInformation对象中
            connectionInformation.setConnection(conn);

            // 计算并记录获取连接所花费的时间（纳秒）
            connectionInformation.setTimeToGetConnectionNs(System.nanoTime() - start);

            // 获取连接成功后，调用监听器的onAfterGetConnection方法
            jdbcEventListener.onAfterGetConnection(connectionInformation, null);
        } catch (SQLException e) {
            // 若获取连接过程中出现异常，记录获取连接所花费的时间，并将异常传递给监听器
            connectionInformation.setTimeToGetConnectionNs(System.nanoTime() - start);
            jdbcEventListener.onAfterGetConnection(connectionInformation, e);
            // 抛出异常
            throw e;
        }

        // 返回包装了真实连接的P6Spy代理连接
        return ConnectionWrapper.wrap(conn, jdbcEventListener, connectionInformation);
    }


    /**
     * 查找与指定URL相匹配的真实JDBC驱动
     *
     * @param url 连接URL
     * @return 真实的JDBC驱动
     * @throws SQLException 如果找不到匹配的驱动
     */
    protected Driver findPassthru(String url) throws SQLException {
        // 注册 passthru 驱动，如果已配置
        P6ModuleManager.getInstance();

        // 提取真实的URL
        String realUrl = extractRealUrl(url);

        // 初始化为null，用于存储找到的匹配驱动
        Driver passthru = null;

        // 遍历已注册的驱动列表
        for (Driver driver : registeredDrivers()) {
            try {
                // 判断当前驱动是否接受给定的真实URL
                if (driver.acceptsURL(extractRealUrl(url))) {
                    // 如果接受，将该驱动赋值给passthru变量，并退出循环
                    passthru = driver;
                    break;
                }
            } catch (SQLException e) {
                // 忽略捕获到的SQLException异常
            }
        }
        // 如果找不到匹配的驱动，则抛出SQLException异常
        if (passthru == null) {
            throw new SQLException("Unable to find a driver that accepts " + realUrl);
        }
        // 返回找到的匹配驱动
        return passthru;
    }

    @Override
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties properties) throws SQLException {
        return findPassthru(url).getPropertyInfo(url, properties);
    }

    @Override
    public int getMajorVersion() {
        // This is a bit of a problem since there is no URL to determine the passthru!
        return 2;
    }

    @Override
    public int getMinorVersion() {
        // This is a bit of a problem since there is no URL to determine the passthru!
        return 0;
    }

    @Override
    public boolean jdbcCompliant() {
        // This is a bit of a problem since there is no URL to determine the passthru!
        return true;
    }

    // Note: @Override annotation not added to allow compilation using Java 1.6
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        throw new SQLFeatureNotSupportedException("Feature not supported");
    }

    public static void setJdbcEventListenerFactory(JdbcEventListenerFactory jdbcEventListenerFactory) {
        P6SpyDriver.jdbcEventListenerFactory = jdbcEventListenerFactory;
    }
}
